#import <appkit/NXImage.h>
#import <appkit/Panel.h>

#import <appkit/appkit.h>
#import <dpsclient/wraps.h>
#import <libc.h>
#import <math.h>
#import <appkit/Slider.h>

#import "PlanetView.h"
#import "Thinker.h"
#import "PlanetWraps.h"

@implementation PlanetView

- createUniverse
{
	int i;
	float dx, xsum=0, dy, ysum=0;

	numplanets = initialNumplanets;
	for (i = 0; i < initialNumplanets; i++)
		[self newPlanet:&myplanets[i] :bounds.size.width: bounds.size.height];

	for (i = 0; i < initialNumplanets-1; i++)
	{
		dx = randBetween(-3, 3);
		dy = randBetween(-3, 3);
		myplanets[i].xv = dx;
		myplanets[i].yv = dy;
		xsum+=dx;
		ysum+=dy;
	}

	// zero out the energy in the universe
	myplanets[i].xv = -xsum;
	myplanets[i].yv = -ysum;

	iterationCount = 0;
	return self;
}

- initFrame:(const NXRect *)frameRect
{
	[super initFrame:frameRect];

	PLinitPlanetDefs();

	initialNumplanets = PLANETS;
	gfactor = 1;
	myplanets = (aplanet *) malloc(MAXPLANETS * sizeof(aplanet));

	[self createUniverse];

	return self;
}

- setGFactor:sender
{
	gfactor = [sender floatValue];
	return self;
}

- oneStep
{
	int i, j;
	double xfactor, yfactor;

	if (++iterationCount > MAXITERATIONS)
	{
		[self createUniverse];
		[self display];
		iterationCount = 0;
	}

	for (i=0; i < numplanets; i++)
	{
		// erase each
		PSsetgray(0);
		PLdrawPlanet(myplanets[i].x, myplanets[i].y,
			SPACEDIST * sqrt(myplanets[i].gravity));

		// move each
		myplanets[i].x += myplanets[i].xv;
		myplanets[i].y += myplanets[i].yv;

		[self constrainPosition:i];

		// draw each
		PSsetgray(1);
		PLdrawPlanet(myplanets[i].x, myplanets[i].y,
			SPACEDIST * sqrt(myplanets[i].gravity));

 		[[self window] flushWindow];

		// perturb each
		for (j = i + 1; j < numplanets; j++)
		{
			float dist = sqrt(SQR(deltaX(i, j)) + SQR(deltaY(i, j))) / SPACEDIST;

			myplanets[i].xv += (xfactor = deltaX(i, j) / 
				(SQR(dist) * dist)) * myplanets[j].gravity * gfactor;
			myplanets[i].yv += (yfactor = deltaY(i, j) / 
				(SQR(dist) * dist)) * myplanets[j].gravity * gfactor;

			myplanets[j].xv -= xfactor * myplanets[i].gravity * gfactor;
			myplanets[j].yv -= yfactor * myplanets[i].gravity * gfactor;

			// check for collisions
			if (dist * SPACEDIST < sqrt(myplanets[i].gravity)
					+ sqrt(myplanets[j].gravity))
			{
				[self merge:i :j];
				continue;
			}
		}
	}

	return self;
}

- constrainPosition:(int)i
{
	if (myplanets[i].x < bounds.origin.x - 20)
	{
		myplanets[i].x = bounds.origin.x + bounds.size.width + 10;
		myplanets[i].xv *= 0.95;
	}
	else if (myplanets[i].x > bounds.origin.x + bounds.size.width + 20)
	{
		myplanets[i].x = bounds.origin.x - 10;
		myplanets[i].xv *= 0.95;
	}

	if (myplanets[i].y < bounds.origin.y - 20)
	{
		myplanets[i].y = bounds.origin.y + bounds.size.height + 10;
		myplanets[i].yv *= 0.95;
	}
	else if (myplanets[i].y > bounds.origin.y + bounds.size.height + 20)
	{
		myplanets[i].y = bounds.origin.y - 10;
		myplanets[i].yv *= 0.95;
	}
	return self;
}

- constrainGravity:(int)i
{
	if (myplanets[i].gravity < 1) myplanets[i].gravity = 1;
	else if (myplanets[i].gravity > MAXGRAVITY) myplanets[i].gravity = MAXGRAVITY;
	return self;
}

- setNumberPlanets:sender
{
	initialNumplanets = numplanets = [sender intValue];

	[numplanetTextField setIntValue:numplanets];

	[self createUniverse];
	[self display];

	return self;
}

- merge:(int)planetA :(int)planetB
{
	float tgrav = myplanets[planetA].gravity + myplanets[planetB].gravity;
	PSsetgray(0);
	PLdrawPlanet(myplanets[planetA].x, myplanets[planetA].y,
			SPACEDIST * sqrt(myplanets[planetA].gravity));

	myplanets[planetA].xv = (myplanets[planetA].xv * myplanets[planetA].gravity + 
		myplanets[planetB].xv * myplanets[planetB].gravity) / tgrav;
	myplanets[planetA].yv = (myplanets[planetA].yv * myplanets[planetA].gravity + 
		myplanets[planetB].yv * myplanets[planetB].gravity) / tgrav;

	myplanets[planetA].gravity = tgrav;
	[self constrainGravity:planetA];

	PSsetgray(0);
	PLdrawPlanet(myplanets[planetB].x, myplanets[planetB].y,
			SPACEDIST * sqrt(myplanets[planetB].gravity));
	PSsetgray(1);
	PLdrawPlanet(myplanets[planetA].x, myplanets[planetA].y,
			SPACEDIST * sqrt(myplanets[planetA].gravity));
	[[self window] flushWindow];

	myplanets[planetB] = myplanets[--numplanets];

	if ((myplanets[planetA].gravity > ((initialNumplanets * DEFAULTGRAV) * .65))
		|| ((randBetween(0,1) <= 0.15) && 
		(myplanets[planetA].gravity > ((initialNumplanets * DEFAULTGRAV) * .33))))
		[self blowPlanet:planetA];

	if (numplanets == 1) iterationCount = MAXITERATIONS - 200;

	return self;


}

- newPlanet:(aplanet *) newplanet :(float)width :(float)height
{
	newplanet->x = randBetween(0,width);
	newplanet->y = randBetween(0, height);
	newplanet->gravity = DEFAULTGRAV;
	newplanet->xv = 0;
	newplanet->yv = 0;
	return self;
}

- blowPlanet:(int)planetNum
{
	int	  i, newplanets, newplanetnum;
	double   newxd, newyd, newradius;
	float oldgrav = myplanets[planetNum].gravity;
	float xc, yc;

	newplanets = MIN(oldgrav, MAXPLANETS-numplanets) - 1;
	newradius = newplanets * 1.5;
	newplanetnum = MIN((numplanets + newplanets), MAXPLANETS);

	for (i = numplanets; i < newplanetnum; i++)
	{
		myplanets[i] = myplanets[planetNum];
		newxd = (xc = sin(PI * 2 * (i - numplanets) / (newplanets))) * newradius;
		newyd = (yc = cos(PI * 2 * (i - numplanets) / (newplanets))) * newradius;

		myplanets[i].x += newxd;
		myplanets[i].y += newyd;
		myplanets[i].gravity = oldgrav / (newplanets+1);
		myplanets[i].xv += xc * SPACEDIST * randBetween(2.85,3.5);
		myplanets[i].yv += yc * SPACEDIST * randBetween(2.85,3.5);
		[self constrainGravity:i];
	}

	PSsetgray(0);
	PLdrawPlanet(myplanets[planetNum].x, myplanets[planetNum].y,
			SPACEDIST * sqrt(myplanets[planetNum].gravity));

	myplanets[planetNum].gravity = oldgrav / (newplanets+1);
	[self constrainGravity:planetNum];
	numplanets = newplanetnum;
	return self;

}

- (const char *)windowTitle
{	return "Orbital Simulation";
}

- inspector:sender
{
    char buf[MAXPATHLEN];
	
    if (!inspectorPanel)
	{
		sprintf(buf,"%s/planet.nib",[sender moduleDirectory:"Planet"]);
		[NXApp loadNibFile:buf owner:self withNames:NO];

		[numplanetSlider setIntValue:numplanets];
		[numplanetTextField setIntValue:numplanets];
    }
    return inspectorPanel;
}

- sizeTo:(NXCoord)width :(NXCoord)height
{
	[super sizeTo:width :height];
	[self createUniverse];
	return self;
}

- drawSelf:(const NXRect *)rects :(int)rectCount
{
	if (!rects || !rectCount) return self;
	
	PSsetgray(NX_BLACK);
	NXRectFill(rects);
	return self;
}

- (BOOL) useBufferedWindow;
{	return YES;
}

@end
